package com.summer.bilibili.aop.throttle;

import cn.hutool.core.util.StrUtil;
import com.summer.bilibili.exception.CustomException;
import com.summer.bilibili.util.RedisUtils;
import com.summer.bilibili.util.SecurityUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;

import java.time.Duration;

@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
public class ThrottleAspect {
    private final RedisUtils redisUtils;

    @Pointcut("@annotation(com.summer.bilibili.aop.throttle.Throttle)")
    public void pointCut () {
    }

    ;

    @Around("pointCut()")
    public Object around ( ProceedingJoinPoint joinPoint ) throws Throwable {

        MethodSignature signature = (MethodSignature) joinPoint.getSignature ( );
        Throttle throttle = signature.getMethod ( ).getAnnotation ( Throttle.class );
        // 安全验证机制
        Long contextUserId = SecurityUtils.getContextUserId ( true ); // 获取当前请求用户id
        //方法全局唯一标记
        String methodPath = signature.toLongString ( );
        String redisKey = "throttle:" + contextUserId + ":" + methodPath;
        // SpEL动态扩展
        if ( StrUtil.isNotBlank ( throttle.key ( ) ) ) {
            MethodBasedEvaluationContext context = new MethodBasedEvaluationContext (
                    joinPoint.getTarget ( ) ,  // 目标对象实例
                    signature.getMethod ( ) ,  // 方法元数据
                    joinPoint.getArgs ( ) ,    // 方法参数值数组
                    new DefaultParameterNameDiscoverer ( ) ); // 参数名发现器
            try {
                Object value = new SpelExpressionParser ( ).parseExpression ( throttle.key ( ) ).getValue ( context );
                if ( value != null ) {
                    redisKey += ":" + value;
                }
            } catch (Exception e) {
                log.error ( "EL表达式错误" , e );
            }
        }
        // 固定时间窗口限流算法
        // Redis计数与限流判定
        Long count = redisUtils.increment ( redisKey , 1 ); // 原子自增，计数
        if ( count != null ) { // 首次请求
            if ( count == 1 ) {
                Duration duration = Duration.of ( throttle.duration ( ) , throttle.timeUnit ( ) );
                redisUtils.expire ( redisKey , duration ); // 设置过期时间 ， 创建时间窗口
            }
            if ( count > throttle.limit ( ) ) { // 超出阈值
                throw new CustomException ( throttle.message ( ) ); // 阻断请求
            }
        }
        return joinPoint.proceed ( ); // 放行
    }
}
